package pl.bristleback.server.bristle.config;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import pl.bristleback.server.bristle.binding.BindingsProcessor;
import pl.bristleback.server.bristle.binding.resolvers.BindingEngine;
import pl.bristleback.server.bristle.binding.resolvers.PropertyValueResolver;
import pl.bristleback.server.bristle.exceptions.BristleRuntimeException;
import pl.bristleback.server.bristle.messages.MessageDispatcher;
import pl.bristleback.server.bristle.token.TokenizerEngine;
import pl.bristleback.server.bristle.utils.ReflectionUtil;

import java.util.HashMap;
import java.util.Map;

/**
 * Bristleback plugin configuration.
 * <p/>
 * Created on: 2011-03-27 13:19:50 <br/>
 *
 * @author Wojciech Niemiec
 * @see pl.bristleback.server.bristle.config.BristleConfigurationElement configuration elements
 */
public class BristleConfiguration {
  private static Logger log = Logger.getLogger(BristleConfiguration.class.getName());

  public static final String PROPERTY_VALUE_RESOLVER_PREFIX = "bristle.resolvers.property.";
  private Map<String, Object> settings;
  private Map<BristleConfigurationElement, Object> configurationInstances;

  /**
   * Loads Bristleback configuration using plugin settings taken from jwebsocket.xml.
   *
   * @param pluginSettings plugin settings taken from jwebsocket.xml.
   */
  public void load(Map<String, Object> pluginSettings) {
    settings = pluginSettings;
    configurationInstances = new HashMap<BristleConfigurationElement, Object>();
    for (BristleConfigurationElement configurationElement : BristleConfigurationElement.values()) {
      Object configurationElementInstance = loadConfigurationElementInstance(pluginSettings, configurationElement);
      configurationInstances.put(configurationElement, configurationElementInstance);
    }
  }

  private Object loadConfigurationElementInstance(Map<String, Object> pluginSettings, BristleConfigurationElement configurationElement) {
    String configurationType = getConfigurationElementType(pluginSettings, configurationElement);
    ClassConfiguration configuration = new ClassConfiguration(configurationElement.getPluginSettingName(), configurationType);
    return ClassConfigurationUtil.getInstanceFromConfiguration(configurationElement.getInterfaceToImplement(), configuration);
  }

  private String getConfigurationElementType(Map<String, Object> pluginSettings, BristleConfigurationElement configurationElement) {
    String settingKey = configurationElement.getPluginSettingName();
    String settingValue = (String) pluginSettings.get(settingKey);
    if (StringUtils.isBlank(settingValue)) {
      return configurationElement.getDefaultImplementationClass().getName();
    }
    return settingValue;
  }

  /**
   * Gets plugin settings map taken from jwebsocket.xml file.
   *
   * @return plugin settings.
   */
  public Map<String, Object> getSettings() {
    return settings;
  }

  /**
   * Gets action annotation processor used to retrieve action classes.
   *
   * @return action annotations processor
   * @see pl.bristleback.server.bristle.config.BristleConfigurationElement#ACTION_ANNOTATION_PROCESSOR
   */
  public ActionAnnotationsProcessor getAnnotationProcessor() {
    return (ActionAnnotationsProcessor) configurationInstances.get(BristleConfigurationElement.ACTION_ANNOTATION_PROCESSOR);
  }

  /**
   * Gets message container resolver, which includes messages senders and message dispatcher.
   *
   * @return message container resolver.
   * @see pl.bristleback.server.bristle.config.BristleConfigurationElement#MESSAGE_CONTAINER_RESOLVER
   */
  public MessageContainerResolver getMessageContainerResolver() {
    return (MessageContainerResolver) configurationInstances.get(BristleConfigurationElement.MESSAGE_CONTAINER_RESOLVER);
  }

  /**
   * Gets message dispatcher.
   *
   * @return message dispatcher.
   * @see pl.bristleback.server.bristle.config.BristleConfigurationElement#MESSAGE_DISPATCHER
   */
  public MessageDispatcher getMessageDispatcher() {
    return (MessageDispatcher) configurationInstances.get(BristleConfigurationElement.MESSAGE_DISPATCHER);
  }

  /**
   * Gets default tokenizer engine used in object to token conversions.
   * Custom tokenizer engine for individual conversion operation can be set in {@link pl.bristleback.server.bristle.token.Tokenize} annotation.
   *
   * @return default tokenizer engine.
   * @see pl.bristleback.server.bristle.config.BristleConfigurationElement#DEFAULT_TOKENIZER
   */
  public TokenizerEngine getDefaultTokenizer() {
    return (TokenizerEngine) configurationInstances.get(BristleConfigurationElement.DEFAULT_TOKENIZER);
  }

  /**
   * Gets binding annotations processor used to retrieve details of binding operations.
   *
   * @return binding annotations processor.
   * @see pl.bristleback.server.bristle.config.BristleConfigurationElement#BINDINGS_PROCESSOR
   */
  public BindingsProcessor getBindingsProcessor() {
    return (BindingsProcessor) configurationInstances.get(BristleConfigurationElement.BINDINGS_PROCESSOR);
  }

  /**
   * Gets default binding engine.
   * Custom binding engine can be set individually for each binding operation
   * in {@link pl.bristleback.server.bristle.binding.Bind} annotation.
   *
   * @return default binding engine.
   * @see pl.bristleback.server.bristle.config.BristleConfigurationElement#DEFAULT_BINDING_ENGINE
   */
  public BindingEngine getDefaultBindingEngine() {
    return (BindingEngine) configurationInstances.get(BristleConfigurationElement.DEFAULT_BINDING_ENGINE);
  }

  /**
   * Gets default value resolver for type given as parameter.
   * Each default resolver can be changed in 'jwebsocket.xml' configuration file
   * by adding plugin setting with key bristle.resolvers.property.[propertyType] (without brackets).
   * Each property mapping annotation can specify custom property value resolver, even for complex object types.
   *
   * @param valueType property value type
   * @return default value resolver for type given as parameter.
   */
  public PropertyValueResolver getDefaultValueResolver(Class valueType) {
    BristleConfigurationElement configurationElement = getValueResolverConfigurationElement(valueType);
    if (configurationElement == null) {
      throw new BristleRuntimeException("Cannot find default value resolver for a class type: " + valueType.getName());
    }
    return (PropertyValueResolver) configurationInstances.get(configurationElement);
  }

  private BristleConfigurationElement getValueResolverConfigurationElement(Class valueType) {
    String settingKey = getValueResolverSettingKey(valueType);
    for (BristleConfigurationElement configurationElement : BristleConfigurationElement.values()) {
      if (settingKey.equalsIgnoreCase(configurationElement.getPluginSettingName())) {
        return configurationElement;
      }
    }
    return null;
  }

  private String getValueResolverSettingKey(Class valueType) {
    String suffix;
    if (valueType.isPrimitive()) {
      suffix = ReflectionUtil.getWrapperClassForPrimitive(valueType).getSimpleName();
    } else {
      suffix = valueType.getSimpleName();
    }
    return PROPERTY_VALUE_RESOLVER_PREFIX + suffix;
  }
}
